<?
/*  class Antispam 
	Класс для защиты форм от спам-роботов. 
	В большинстве случаев старается работать незаметно (без необходимости грфической каптчи, сессии и Javascript), распознавая спам-роботов по признакам запроса. При возникновении "подозрений" на спам-робота - включает сессию, грфическую каптчу и прочие добавки.
	
	Зависимости класса: может генерировать HTML-код, который использует внешний файл captcha.php для генерации картинки каптчи.


	Методы:
	
	Antispam::captcha($params) - возвращает HTML-код, который нужно вставить внутри формы, для проверки на спам-бота. Если текущий пользователь распознан (вероятно) как человек (не робот), то используются скрытые поля.
	
	Antispam::error() - возвращает текст ошибки, если спам проверка не пройдена
	
	~ Antispam::is_bot($params) - возвращает true, если рапознан робот
	~ Antispam::is_human($params) - возвращает true, если рапознан человек 
	~ Antispam::check($params) - возвращает true, если рапознан человек
	
	Вообще-то может быть 4 вида ответа: 
	* точно человек 
	* точно бот или спаммер 
	* возможно бот, подозрительно 
	* ещё не неопределено
	
	Также должно возвращатся адекватное сообщение об ошибке для пользователя (заполните секретный код, введите буковки с картинки, некорректное содержимое полей, подозрение на спам, заполните ещё раз, Если Вы не робот- отправьте форму ещё раз и т.д. ...)
	
	Antispam::init($options) - устанавливает параметры для работы класса
	
	
	~ Antispam::register_captcha($params) - регистрирует параметры новой каптчи для использования
	
	

*/

// инициализируем параметры антиспама
Antispam::init(array(
	
	'form_period'=>60*60, // 1час  -  макс. период действенности формы от момента её формирования, в течение которого принимаются данные от формы, в секундах 
	'logging'=>1, // 1 = включить логгирование проверок (для последующего анализа признаков спам-роботов). Каждый вызов Antispam::error() будет записывать в лог-файл результаты проверки и дампы переменных
	'log_max_size'=>5000000, // максимальный размер лога в байтах, после которого он обнуляется
));

class Antispam {
	
	// параметры работы антиспама 
	static $options = array(); 
	
	
	// инициализация класса
	static function init($options)
	{
		self::$options = array_merge(self::$options, $options);
	}
	
	
	// возвращает HTML-код каптчи для формы
	static function captcha($form_id) 
	{
		$result='';
		
		
		// сохраняем время отправки формы в виде имени поля
		// это поле должно остаться пустым (глупые роботы заполняют все поля)
		// а также время отправки формы не должно превышать допустимое
		$form_code = time();
		$result .="
			<span style='display:none' >
			<INPUT name='form_code_{$form_code}' value='' >
			</span>
			";
		
		return $result;
	}
	
	/*** 	
		Antispam::error()  - функция для использования в контроллере формы. Проверяет запрос на признаки спама и спам-роботов. (Для корректной проверки, форма должна содержать вызов Antispam::captcha() )
		
		Если проверка пройдена - возращает пустую строку.
		Если антиспам-проверка не пройдена, возвращает сообщение об ошибке. 
		
		Примеры исследований статистики по частым признакам спам-ботов: 
		http://blgo.ru/blog/2009/06/05/antispam/
		http://www.varvashenia.ru/ru/cms/spam_in_form/
		http://guzei.com/www/form/
		http://snook.ca/archives/other/effective_blog_comment_spam_blocker

	***/
	static function error() 
	{
		$error = '';
				
		// ищем поле, в котором содержится время формирования формы
		$code_field=$code='';
		foreach ($_POST AS $key=>$val)
			if (strstr( '^'.$key, '^form_code_')) 
			{
				$code_field = $key; 
				$code = str_replace ( '^form_code_', '', '^'.$key);
				break;
			}
		
		
		// если поле с кодом в имени не найдено
		// или - если это поле есть и оно заполненно
		// то - вероятно это спам-робот
		if (!$code_field OR !empty($_POST[$code_field]) OR !is_numeric($code))
			$error .= 'Параметры запроса некорректны, возможно, Вы робот. ';
		
		
		// убеждаемся что форма была отправлена не слишком давно
		if ($code && time()-$code > self::$options['form_period'])
			$error .= 'Время ожидания формы превышено. Попробуйте ещё раз. ';
		
		
		// если есть ошибки -  сообщаем, что доступ запрещен (вдруг роботы проверяют 200 OK)
		if ($error) header('HTTP/1.1 403 Forbidden');
		
		// другие варианты HTTP-ответов:
		// 400 Bad Request 
		// 401 Unauthorized
		// 403 Forbidden 
		// 405 Method Not Allowed) 
		// 406 Not Acceptable 
		// 409 Conflict 
		// 412 Precondition Failed 
		// header() examples: http://www.jonasjohn.de/snippets/php/headers.htm

		// логгируем результаты проверки
		self::log($error);
		
		return $error;
		
	}
	
	// логгирование результатов проверки - для последующего анализа ошибочных разрешений и блокировок 
	static function log($error_msg)
	{
		// если логирование выключено, возвращаемся
		if(empty(self::$options['logging'])) return;
		
		// задаем имена для лог-файлов
		$log_ok = SYSTEM_DATA_DIR .'/logs/antispam_good.log';
		$log_notok = SYSTEM_DATA_DIR .'/logs/antispam_bad.log';
		$log = $error_msg? $log_notok : $log_ok;
		
		// если лога нет, или он слишком большой - создаем пустой лог
		if (!file_exists($log) 
			|| filesize($log) > self::$options['log_max_size'] 
			) 
			file_put_contents($log, '');
		
		
		
		// формируем дамп всей доступной информации для последующего анализа
		$txt = "\r\n\r\n"
			."==========================================================\r\n"
			.'Date: '.date('d.m.Y, H:i:s')."\r\n"
			.'URL: '. $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\r\n"
			.($error_msg? 'ERROR: '.$error_msg : 'CHECK OK')."\r\n"
			.'IP: '.$_SERVER['REMOTE_ADDR']."\r\n"
			.'User_Agent: '.$_SERVER['HTTP_USER_AGENT']."\r\n\r\n"
			.'$_POST: '.print_r($_POST, 1)."\r\n"
			//.'Headers: '.print_r(getallheaders(), 1)."\r\n"
			.'Headers: '.print_r(getallheaders(), 1)."\r\n"
			.'$_GET: '.print_r($_GET, 1)."\r\n"
			.'$_COOKIE: '.print_r($_COOKIE, 1)."\r\n"
			.'$_SERVER: '.print_r($_SERVER, 1)
		;
		
		// добавляем запись в конец лога
		file_put_contents($log, $txt, FILE_APPEND );
		

		
	
	}
	

}


if (!function_exists('getallheaders'))
{
  function getallheaders()
  {
    foreach ($_SERVER as $name => $value)
    {
      if (substr($name, 0, 5) == 'HTTP_')
      {
         $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
      }
    }
    return $headers;
  }
}




/*
// генерирует HTML-код для каптчи
	static function captcha()
	{	// TODO: проверять наличие шаблона 'mailform_capcha'
		
		$A=rand(0,10); $B=rand(0,10);
		$captcha_hash = md5($A+$B);
		
		$captcha = array(
			'type'	=> 'captcha',
			'name'	=> 'captcha_code',
			'label'	=> 'Проверочный код (защита от роботов)',
			'field'	=> "
				<font color=red >$A+$B=</font>
				<input type='text' name=captcha_code style='width:70px;'>
				<input type='hidden' name=captcha_hash value='$captcha_hash' >
				",
			
		
		);
		
		return (object)$captcha;		
	}
	
	// проверять капча код, переданные через $_POST
	static function check_captcha()
	{	
		$captcha_code = POST('captcha_code');
		$captcha_hash = POST('captcha_hash');
		
		if (md5($captcha_code)==$captcha_hash) 
			return true;
		else 
			return false;
		
	}

*/




/*	
	
	Элеметы капчи:
	
	Шаблон формы:
	- название поля: антиспам, заитный код, введите картинку, я не робот, число на картинке, защита от роботов, введите цифры слева, подтверждите что вы не робот, это не спам, 
	Шаблон капчи:
	- поле для ввода (3-6 символов, или галочка)
	- задание - картинка или вопрос

	Нужно сделать возможность, чтобы через настройки ... сайта / модуля можно было сменить уровень сложности антиспам-защиты и также тип капчи (картинка, текст-вопрос, графический тест) 
	

*/

/*
Способы для защиты формы от спам-ботов (можно внедрить в формы для гостей - формы связи, опросы, анкеты, комментарии, регистрации)

http://blgo.ru/blog/2009/06/05/antispam/
 

* скрытое поле «email»
* зашифрованные имена полей (привязываются к текущему часу, ip, и useragent'у)
*  Кстати, в HTML можно выносить фиктивные сессии, для отслеживания попыток взлома на их начальной стадии. 

* время заполнения формы - не менее ~1-5сек и не более 30 минут. Если вермя неверное - возвращение к форме с выдачей ошибки.

* Скрытое поле   Этот метод может показаться странным, но он, похоже, работает. К форме добавляется скрытое поле (скрытое в смысле display:none). Если поле заполнено, то пользователь считается ботом.  Удивительно, но многие простенькие боты заполняют все неизвестные поля.

* Форма генерируется с помощтю JS

* Блокирование определённых user-agent
* Ловушка» для ботов -  Суть этого метода заключается в создании специального раздела сайта вроде "/bot/guestbook". Ссылка на этот раздел не видна для пользователя, однако если бот зайдёт в этот раздел и сделает там хоть что-нибудь, то его IP тут же блокируется. Раздел должен содержать лакомые для бота слова вроде «email», «submit», «add comment» и тому подобные. Файл «robots.txt» предупреждает хороших ботов.

* Хеширование pyfxtybq формы  -   Перед сабмитом формы вычисляется (JS) хеш полей формы и добавляется в одно из специальных скрытых полей. Сервер проверяет значение хеша.

* Использование прозрачных кнопок -   У формы есть несколько кнопок типа <input type="image". Лишь на одной из картинок написан текст, остальные — прозрачные. Для сабмита пользователь должен нажать на картинку с текстом.

* Использование сторонних сервисов -  Можно туннелировать трафик через специальный сервис, предназначенный для анализа контента на спам. Примером такого сервиса может быть Akismet



* проверять правильность заполнениия каждого поля - длина (например: более 2 и менее 50), тип данных, отсутствие тегов и ссылок, 



1. js-сабмит (95% ботов не проходят)
2. скрытые поля (не hidden, а нормальные поля взятые в блок с display: none) (98% ботов их заполняют, потом проверяется если поле было заполненно значит бот). сделать скрытое поле (лучше скрыть с помощью CSS) c именем name, login, user, email… человек этого поля не увидит и не заполнит, а вот робот мимо такого сладенького поля не пройдёт. Защита простая: кто заполнил — того в /dev/null :-) 


3. временной hidden (например c name=«phone» и value=«текущее время») — если разница между временем в хиддене и текущем времене меньше 2 секунд или больше 24 часов — ты однозначно бот (спам бот может работать в таких условиях один день — потом 100% ботов идут лесом)
4. поле hidden создаещееся js-ом при событии onfocus любого из полей формы (value этого поля лучше менять каждый день, т.е. функция от даты)
5. Возвратный AJAX: отправка формы AJAX'ом в случае успешной отправки, возвращается сгенерированное число, которое отправляется ещё раз AJAX'ом назад — что является подтверждением сабмита (этот вариант супер-надежный, отсеивает 100% ботов, но кроме ботов идут лесом все пользователи с отключенным js и реализовать его не очень-то просто)


 
 
 
* картинка-капча - цифры, буквы, полд углом, искаженные, присыпанные цветным мусором, и т.п.

* задавать текстовый вопрос с известным или ограниченными множеством ответов. Какого цвета апельсин? Какой сейчас год? пять + шесть =

* выводить текстом математическое выражение 2+3= ,  10-7=   , 0+1=

* использовать в форме поля, которые будут скрыты с помощью CSS и должны иметь пустое или заранее определеное значение. роботы будут заполнять эти поля, а люди нет, таким образом отсеем роботов.  Цитата: в скрытый средствами css div — включал поле «ConfirmEmail». Боты его старательно заполняют, а люди — нет

* к именам полей форм добавлять sessionID - таким образом робот не сможет указать правильные имена для полей форм.

* вибрать одну/несколько из нескольких каринок по вопросу (ниживые, живые, и т.п.)

* каринка с искажением - нужно кликнуть на место искажения изображения. картинки модно брать с гугла. http://habrahabr.ru/blogs/webdev/17044/


* таймаут для постинга для посетителя - в каждой форме передавать уникальный id формы + сохранять его в сессии - принимать данные от формы только если её UID не потерял уникальность, после отправки формы обновлять formUID 

* таймаут для постинга с одного IP 


* после отправки формы сохранять у посетителя куку - либо с временем следующей возмодной отправки, либо с ID посетителя (т.юе. стартовать сессию и в сессии задавать время следующего возмодного постинга)

* прогрессивный таймаут - для второго послания - 1 мин, для 3го послания - 10 мин, для 4го послания - 1 час, и т.д.



* принимать данные форм только от разрешенных URL-рефферов

* разделять процесс отправки формы на 2 части - пред.просмотр и отправка. 


* от запроса к запросу варьировать набор полей формы (добавлять фиктивные поля) - таким образом робото не сможет угать правильный набор полей, передаваемых на сервер, при котором форма отправиться. 

* показывать капчу только если куки не сохраняются. иначе - сохранять в куках ID посетителя, ли формы, или хэш сайта, или хэш даты, и принимать форму только если роона передает корректный хэш из куках
  
* вместо капчи - просить кликнуть на определенно место на картинке и определять кординаты клика - и отправлять их не сервер

* проверять содержимое полей на спам слова -  можно отшивать все мессаги, где присутствуют строчки типа [url= и <a href=	(Если, конечно, функционалом системы комментирования это не предусмотрено)  Часто такие мессаги и постят боты, во всяком случае, в моей практике. 


* CSS History Hack называется. Это такой очень простой и изящный способ узнать, посещал ли юзер какой-то конкретный сайт. Ставим на страницу ссылку на проверяемый сайт, делаем для неё разные стили a и a:visited, затем при заходе пользователя смотрим, как эта ссылка ему отобразилась и делаем соответствующий вывод. Даже без яваскрипта работает, если сделать a:visited { background: url(...) };. 	Можно попробовать использовать этот хак как дополнение к каптче. Если пользователь, зашедший к вам на сайт, подвержен этому хаку, и если он заходил на гугл, яндекс или там лайвджорнал.ком какой-нибудь, то он 100% не бот, потому что у бота нет браузера. Осталось только придумать секурный способ передать на сервер эту информацию, и всё, можно живым людям не надоедать проверками.


* рейтинговать степень "роботичности" посетителя (ip, рефер, наличие кук, спам-символы в прошлых сообщениях, время от прошлой отправки, куки вкл, js вкл, юзер агент есть, хэш код есть, правильаня последовательногсть поелй формы, не превышение ограничения длины для значения поля формы) и в зависимости от этого признака (устанавливать его в сессии и побъекте $User) варьировать силу защиты автоматически 
 Примеры исследований статистики по частым признакам спам-ботов: 
	http://www.varvashenia.ru/ru/cms/spam_in_form/
	http://guzei.com/www/form/
	http://snook.ca/archives/other/effective_blog_comment_spam_blocker
	

* http://habrahabr.ru/blogs/spam/74007/ - выстраивать картинки в горизонталь


* Наткнулся тут недавно на одном сайте на способ защиты от ботов без использования каптчи и javascript. Все очень просто — достаточно добавить скрытое поле с символом кодированным в HTML сущность (например © — и т. д.). Дело в том что браузер найдя такой символ преобразует его перед отправкой в обычный, а робот использующий парсер форм так и отправить закодированным (причем у меня есть свой парсер форм и он сделал бы именно так). При проверки формы достаточно просто посмотреть длину строки в этом поле. Если отправлял человек то она будет равна числу символов в строке, а если нет значительно больше.  Так что такое решение пусть и не обеспечивает серьезной защиты но вполне может применяться в тех случаях если что то более сложное использовать нельзя.


***********************
Мои требования к капче
***********************

Защиты формы от спама - должна быты независимо от формы! Или одключаться независимо.  Все шаблон для форм связи, комментария, регистрации, логина - должны верстаться так, как буд-то там нет капчи, но также должны исопльзовать ФУНКЦИИ - для вывода какой-то части формы - старта, конца, субмита, полей, ЧТОБЫ система могла изменять вывод какой-то функции и добавлять каптчу. 

Админ или дизайнер или сама система должны иметь возмодность сменить вид капчи - в процессе борьбы против спаметром-  Это можно делать через шаблон дизайн формы  (указывать тип каптчи и её параметры), - !!!или папке шаблонов сайта - создать там файл captcha.php - шаблон параметров для вывода каптчи   или в настройках сайта (указывтаь тип каптчи), или через модуль Каптча - выбирать в натсройках используемыю каптчу и её параметры

Видимая капча должна легко подстраиваться под дизайн формы силами диазйнера. Т.е. её элементы должны быть легко настраиваемые с помощью стилей. И стили должны быть указаны.  Также её Лэйбл (название) и оставшийся код - можно легко извлекать из результата генерации. Но это не обязательно. По умолчанию, капча - это один блок кода, генерируемый по входным параметрам. Генератору кптчи можно передавть шаблон дляч вывода каптчи.

На малопосещаемых сайтах капчи вообще не олжно быть - для удобства пользователей. Для зарегистрирвоаных или проверенных постеителеях - тоже не должно быть капчи. Удобство посетителей - превыше 1% спама.

Модуль "Капча" - будет иметь настройки для ретингования роботов и спам сообщений, выбор тип капчи, загрузки своих картинок и заданий для капч.

Начальная капча в формах должна быть макисмально автоматичская и невидимая.

Каптча должна успешно работать - если отключены JS и Куки.  В идеале капча не дожна быть зависима от JavaScript и Cookies, а елси зависимолсть есть - то нужно сообщать об этом.

Функционал каптчи в ядре должн быть поначалу с невидимой капчей, и при появлении признаков подозрительности (время обработки формы, время с последней отправки формы, спам слова и полех формы, неверные поля формы, неверные ожидаемые значения) - сообщать об ошибке и то же время, отдавать код ~ 403

Удобный функционал и верстку всех форм (форма связи, комментарии, логин, регистрация) без капчи. Потом внедрить невидимую капчу. Потом - внедрить видимую капчу при высоком рейтинге роботичности.

*/